export class MipsConverter {
  calc_base(layer: any) {
    return ["move $t0, $t2"].concat(
      Array.from({ length: layer }).map((v) => "lw $t0, ($t0)")
    );
  }

  line_map = {
    opr: [
      [
        // 0
        ["move $sp, $t2", "addi $t0, $t2, 4", "lw $t2, ($t0)", "jr $ra"],
        ["lw $t0, ($sp)", "sub $t0, $zero, $t0", "sw $t0, ($sp)"],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "add $t0, $t1, $t0",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "sub $t0, $t1, $t0",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "mult $t0, $t1",
          "mflo $t0",
          "sw $t0, ($sp)",
        ],
        // 5
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "div $t1, $t0",
          "mflo $t0",
          "sw $t0, ($sp)",
        ],
        ["lw $t0, ($sp)", "andi $t0, $t0, 0x0001", "sw $t0, ($sp)"],
        [],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "sub $t0, $t1, $t0",
          "sltiu $t0, $t0, 1",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "sub $t0, $t1, $t0",
          "sw $t0, ($sp)",
        ],
        // 10
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "slt $t0, $t1, $t0",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "addi $t1, $t1, 1",
          "slt $t0, $t0, $t1",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "slt $t0, $t0, $t1",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "subu $sp, $sp, 4",
          "lw $t1, ($sp)",
          "addi $t0, $t0, 1",
          "slt $t0, $t1, $t0",
          "sw $t0, ($sp)",
        ],
        [
          "lw $t0, ($sp)",
          "li $v0, 1",
          "subu $sp, $sp, 4",
          "move $a0, $t0",
          "syscall",
        ],
        // 15
        ["li $a0, 0xA", "li $v0, 0xB", "syscall"],
        ["li $v0, 5", "syscall", "addiu $sp, $sp, 4", "sw $v0, ($sp)"],
      ],
    ],
    int: [(v: number) => [`addiu $sp, $sp, ${4 * v}`]],
    lit: [
      (v: number) => ["addiu $sp, $sp, 4", `li $t0, ${v}`, `sw $t0, ($sp)`],
    ],
    cal: (l: number, p: number) =>
      this.calc_base(l).concat([
        "addiu $sp, $sp, 16",
        "sw $ra, -12($sp)",
        "sw $t0, -8($sp)",
        "sw $t2, -4($sp)",
        "subu $t2, $sp, 8",
        `jal lb${p}`,
        "lw $ra, -4($sp)",
        "subu $sp, $sp, 8",
      ]),
    lod: (l: number, v: number) =>
      this.calc_base(l).concat([
        `addi $t0, $t0, ${v * 4}`,
        "lw $t0, ($t0)",
        "addiu $sp, $sp, 4",
        "sw $t0, ($sp)",
      ]),
    sto: (l: number, v: number) =>
      this.calc_base(l).concat([
        `addi $t0, $t0, ${v * 4}`,
        "lw $t1, ($sp)",
        "subu $sp, $sp, 4",
        "sw $t1, ($t0)",
      ]),
    jmp: [(p: number) => [`j lb${p}`]],
    jpc: [
      (p: number) => [
        "lw $t0, ($sp)",
        "subu $sp, $sp, 4",
        `beq $t0, $zero, lb${p}`,
      ],
    ],
  };

  toMips(op: string | number, p1: string | number, p2: string | number):string[] {
    const root = this.line_map[op];
    if (typeof root === "function") {
      return root(p1, p2);
    } else if (typeof root[p1] === "function") {
      return root[p1](p2);
    } else {
      return root[p1][p2];
    }
  }

  convert(pcode: [string, number, number][]): string[] {
    let result = [].concat(
      ...pcode.map(([op, p1, p2]: [string, number, number], i: number) =>
        [`lb${i}:`].concat(this.toMips(op, p1, p2))
      )
    );
    // 删除不使用的label
    const labels = {};
    result.forEach((v) => {
      const temp = /lb\d+(?!(:|\d))/.exec(v);
      if (temp) {
        temp.forEach((x) => (labels[x] = (labels[x] ? labels[x] : 0) + 1));
      }
    });
    result = result.filter(
      (x) => !x.startsWith("lb") || x.substr(0, x.length - 1) in labels
    );
    // 删除块内冗余的存入>取出指令
    for (let i = 0; i < result.length - 1; ++i) {
      if (result[i] === "sw $t0, ($sp)" && result[i + 1] === "lw $t0, ($sp)") {
        result[i + 1] = "";
        ++i;
      }
    }
    result = result.filter((x) => x);
    return [".text", "main:", "move $t2, $sp"].concat(result);
  }
}
